package mastermind;

import java.util.Arrays;
import java.util.Collections;
import java.util.Random;

public class Population {
    
    public static final double CROSSOVER_POINT1 = 0.3;
    public static final double CROSSOVER_POINT2 = 0.7;
    private static final double CROSSOVER_RATE = 0.5;
    private static final double MUTATION_RATE = 0.03;
    private static final double PERMUTATION_RATE = 0.03;
    private static final double INVERSION_RATE = 0.02;
    
    private int generation = 0;
    private Genome[] population;
    private int[] fitness;
    private Double[] fitnessRatio;
    
    private int size;
    
    private Mastermind game;
    
    public Population(int size, Mastermind game) {
        this.size = size;
        this.game = game;
        
        population = generatePopulation();
        fitness = new int[size];
        fitnessRatio = new Double[size];
    }

    /**
     * Generuje startową populację.
     * 
     * @return Wylosowana populacja
     */
    private Genome[] generatePopulation() {
        population = new Genome[size];
        
        for (int i = 0; i < size; i++) {
            Genome genome = new Genome();
            
            while (Arrays.asList(population).contains(genome)) {
                genome = new Genome();
            }
            
            population[i] = genome;
        }
        
        return population;
    }

    /**
     * Kolejna generacja. 50% najsłabszych osobników jest eliminowana, a ich
     * miejsce zastępuje potomstwo pozostałej części.
     */
    public void evolve() {        
        // Co 15 generacji generuj nową populację.
//        if (generation % 15 == 0) {
//            population = generatePopulation();
//        }
        
        Random random = new Random();
        Genome[] nextGeneration = new Genome[size];
        
        Double[] orderedFitnessRatio = fitnessRatio;
        Arrays.sort(orderedFitnessRatio, Collections.reverseOrder());
        orderedFitnessRatio = Arrays.copyOf(orderedFitnessRatio, size / 2);
        
        for (int i = 0; i < orderedFitnessRatio.length; i++) {
            int index = Arrays.asList(fitnessRatio).indexOf(orderedFitnessRatio[i]);
            
            if (i == 0) {
                System.out.println(population[i] + " " + orderedFitnessRatio[i]);
            }
            
            if (index > -1) {
                nextGeneration[i] = population[index];
                nextGeneration[i + (size / 2)] = population[index].clone();
                fitnessRatio[index] = null;
            }
        }
        
        // Krzyżowanie 2-punktowe
        for (int i = size / 2; i < size; i += 2) {
            if (random.nextDouble() < CROSSOVER_RATE) {
                nextGeneration[i].crossover(nextGeneration[i + 1]);
            }
        }
        
        for (int i = size / 2; i < size; i++) {
            Genome genome = nextGeneration[i];
            
            // Mutacja
            if (random.nextDouble() < MUTATION_RATE) {
                genome.mutate();
            }
            
            // Permutacja
            if (random.nextDouble() < PERMUTATION_RATE) {
                genome.permutate();
            }
            
            // Odwracanie
            if (random.nextDouble() < INVERSION_RATE) {
                genome.inverse();
            }
            
            // Jeśli osobnik istniał w poprzedniej generacji - wygeneruj nowy
            if (Arrays.asList(population).contains(genome)) {
                genome = new Genome();
            }
            
            nextGeneration[i] = genome;
        }
                
        population = nextGeneration;
    }
    
    /**
     * Liczy fitnes genomów z aktualnej populacji.
     * 
     * @return Zwraca NULL lub genom, który jest identyczny z szukaną sekwencją.
     */
    public Genome calculateFitness() {
        int fitness_sum = 0;
        int correctPlaces, correctColors;
               
        for (int i = 0; i < size; i++) {
            Genome genome = population[i];
            
            if (game.isCorrect(genome)) {
                return genome;
            }
            
            correctPlaces = game.countCorrectPlaces(genome);
            correctColors = game.countCorrectColors(genome) - correctPlaces;
            
            fitness[i] = correctPlaces * 2 + correctColors;
            fitness_sum += fitness[i];
        }
        
        for (int i = 0; i < size; i++) {
            fitnessRatio[i] = fitness[i] / (double) fitness_sum;
        }
        
        generation++;
        
        return null;
    }

    public int getGeneration() {
        return generation;
    }
    
}
